﻿using Games.Common;
using Games.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace Games.FlipBlocks
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class FlipPage : ContentPage
    {
        IDataBase database = DependencyService.Get<IDataBase>();
        private readonly GameInfo gameInfo;
        private readonly Game game;
        int cellSize;
        double layoutWidth, layoutHeight;
        Dictionary<int, Cell> cells = new Dictionary<int, Cell>();
        Dictionary<int, Cell> nexts = new Dictionary<int, Cell>();
        private Cell flip;
        private bool gameInited;
        private uint len = 120;
        private bool inAnimation = false;

        internal FlipPage(GameInfo gameInfo)
        {
            InitializeComponent();
            BindingContext = this.gameInfo = gameInfo;
            this.game = new Game();
            database.CreateTable<FlipRecord>();
        }
        void OnLayoutSizeChanged(object sender, EventArgs args)
        {
            Layout layout = sender as Layout;
            layoutWidth = layout.Width;
            layoutHeight = layout.Height;

            if(!gameInited)
            {
                game.Init();
                InitLayout();
                gameInited = true;
            }
        }

        void InitLayout()
        {
            var sizew = (int)((layoutWidth - 6) / game.Width);
            var sizeh = (int)((layoutHeight - 6) / game.Height);
            cellSize = Math.Min(sizew, sizeh);
            var layWidth = game.Width * cellSize;
            var layHeight = game.Height * cellSize;
            board.WidthRequest = layWidth + 6;
            board.HeightRequest = layHeight + 6;

            var xMargin = (layoutWidth - layWidth - 6) / 2.0;
            var yMargin = (layoutHeight - layHeight - 6) / 2.0;
            board.Margin = new Thickness(xMargin, yMargin);
            absoluteLayout.WidthRequest = layWidth;
            absoluteLayout.HeightRequest = layHeight;
            absoluteLayout.Margin = new Thickness(3);
            for(int i = 0; i < game.Width * game.Height; i++)
            {
                GetCell(i).ClearFast();
            }
            //foreach(var item in cells.Values)
            //{
            //    item.ClearFast();
            //}
            GetCell(game.Current).Set(game.CurrentColorID);
            GetCell(game.Current).Focus();
            MoveFlip();
            SetNext();
            gameInfo.BroomCount = game.BroomCount;
            gameInfo.Score = game.Score;
            TimeCounter.Start();
            btnReStart.IsEnabled = false;
        }
        private void SetNext()
        {
            var nexts = game.GetNexts();
            for(int i = 0; i <= nexts.Count; i++)
            {
                if(i < nexts.Count)
                {
                    GetNext(i).Set(nexts[i]);
                }
                else
                {
                    GetNext(i).Set(nexts[i - 1]);
                }
            }
            GetNext(0).Focus();
        }

        private async Task RefreshNext()
        {
            var nextColor = game.GetNexts().Last();
            var n0 = GetNext(0);
            var n1 = GetNext(1);
            var n2 = GetNext(2);
            var n3 = GetNext(3);
            n0.Focus(false);
            n3.Set(nextColor);
            await Task.WhenAll(
                    n1.TranslateTo(38, n1.TranslationY, len),
                    n2.TranslateTo(38, n2.TranslationY, len)
                );
            n0.Set(n1.ID);
            n0.Focus();
            n1.Set(n2.ID);
            n1.TranslationX = 0;
            n2.Set(n3.ID);
            n2.TranslationX = 0;
        }

        private void RefreshBomb()
        {
            var bs = game.GetBombs();
            foreach(var item in bs)
            {
                GetCell(item.Key).Bomb(item.Value);
            }
        }

        private Cell GetNext(int index)
        {
            if(!nexts.ContainsKey(index))
            {
                var cell = new Cell();
                var col = 2 - index;
                if(col < 0)
                {
                    col = 0;
                }
                Rectangle rect = new Rectangle(col * 38 + 1, 1, 38, 38);
                gdnexts.Children.Add(cell);
                AbsoluteLayout.SetLayoutBounds(cell, rect);
                nexts.Add(index, cell);
                if(index == 3)
                {
                    gdnexts.LowerChild(cell);
                }
            }
            return nexts[index];
        }
        private Cell GetCell(int index)
        {
            if(index < 0 || index >= game.Width * game.Height)
            {
                return null;
            }
            if(!cells.ContainsKey(index))
            {
                var ncell = new Cell();
                absoluteLayout.Children.Add(ncell);
                cells.Add(index, ncell);
            }
            var cell = cells[index];
            var col = index % game.Width;
            var row = index / game.Width;
            Rectangle rect = new Rectangle(col * cellSize, row * cellSize, cellSize, cellSize);
            AbsoluteLayout.SetLayoutBounds(cell, rect);
            return cell;
        }
        private async Task AnimationFlip(int currentColor, Direction? direction)
        {
            flip.Set(currentColor);
            var tasks = new List<Task>();
            tasks.Add(flip.SetTo(game.CurrentColorID, len));
            switch(direction)
            {
                case Direction.Right:
                    flip.AnchorX = 0.5;
                    flip.AnchorY = 1;
                    tasks.Add(flip.RotateYTo(180, len));
                    tasks.Add(flip.TranslateTo(cellSize, 0, len));
                    break;
                case Direction.Left:
                    flip.AnchorX = 0.5;
                    flip.AnchorY = 0;
                    tasks.Add(flip.RotateYTo(-180, len));
                    tasks.Add(flip.TranslateTo(-cellSize, 0, len));
                    break;
                case Direction.Up:
                    flip.AnchorX = 0;
                    flip.AnchorY = 0.5;
                    tasks.Add(flip.RotateXTo(180, len));
                    tasks.Add(flip.TranslateTo(0, -cellSize, len));
                    break;
                case Direction.Down:
                    flip.AnchorX = 1;
                    flip.AnchorY = 0.5;
                    tasks.Add(flip.RotateXTo(-180, len));
                    tasks.Add(flip.TranslateTo(0, cellSize, len));
                    break;
                default:
                    break;
            }
            await Task.WhenAll(tasks);
        }
        private void MoveFlip()
        {
            if(flip == null)
            {
                flip = new Cell();
                absoluteLayout.Children.Add(flip);
            }
            flip.TranslationX = 0;
            flip.TranslationY = 0;
            flip.RotationX = 0;
            flip.RotationY = 0;
            var col = game.Current % game.Width;
            var row = game.Current / game.Width;
            Rectangle rect = new Rectangle(col * cellSize, row * cellSize, cellSize, cellSize);
            AbsoluteLayout.SetLayoutBounds(flip, rect);
            flip.Focus();
        }
        async void OnSwiped(object sender, SwipedEventArgs e)
        {
            if(game.IsOver())
            {
                return;
            }
            if(inAnimation)
            {
                return;
            }
            inAnimation = true;
            var currentColor = game.CurrentColorID;
            var dir = e.Direction.ToDirection();
            MoveFlip();
            GetCell(game.Current).Focus(false);
            var sames = game.Flip(dir);
            if(sames == null)
            {
                RefreshBomb();
                GetCell(game.Current).Focus();
                inAnimation = false;
                return;
            }
            var (bombs, bombClears) = game.Bomb();
            var tasks1 = new List<Task>();
            tasks1.Add(AnimationFlip(currentColor, dir));
            tasks1.Add(RefreshNext());
            await Task.WhenAll(tasks1);
            inAnimation = false;
            GetCell(game.Current).Focus();
            GetCell(game.Current).Set(game.CurrentColorID);
            var tasks = new List<Task>();
            if(sames.Count >= 2)
            {
                foreach(var item in sames)
                {
                    tasks.Add(GetCell(item).Clear(len * 3 / 2));
                }
            }
            RefreshBomb();
            if(bombClears != null && bombs != null)
            {
                foreach(var id in bombs)
                {
                    tasks.Add(GetCell(id).ScaleTo(3, len));
                }
                foreach(var item in bombClears)
                {
                    tasks.Add(GetCell(item).Clear(len));
                }
                if(bombClears.Contains(game.Current))
                {
                    flip.BackgroundColor = Color.Transparent;
                }
            }
            await Task.WhenAll(tasks);
            if(bombs != null)
            {
                foreach(var id in bombs)
                {
                    GetCell(id).Bomb(0);
                }
                if(bombClears.Contains(game.Current))
                {
                    GetCell(game.Current).ClearFast();
                    GetCell(game.Current).Focus();
                }
            }
            gameInfo.BroomCount = game.BroomCount;
            gameInfo.Score = game.Score;
            if(game.IsOver())
            {
                await ShowFinish();
                btnReStart.IsEnabled = true;
            }
        }

        private async void TapGestureRecognizer_Tapped(object sender, EventArgs e)
        {
            if(game.IsOver() || game.BroomCount <= 0)
            {
                return;
            }
            game.RemoveFromNext();
            gameInfo.BroomCount = game.BroomCount;
            await RefreshNext();
        }
        async Task ShowFinish()
        {
            var record = await SaveRecordAsync();
            await DisplayAlert("加油", $"游戏结束 {record.Message}", "OK");
        }
        async Task<Record> SaveRecordAsync()
        {
            var dur = TimeCounter.Stop();
            var cRecord = new FlipRecord()
            {
                Score = game.Score,
                CreateTime = DateTime.Now,
                Duration = dur
            };
            await database.SaveRecordByDiff(cRecord, 100,
                o => true,
                list => list.OrderByDescending(x => x.Score).ThenBy(x => x.Duration).ThenByDescending(x => x.ID),
                o => o.Score < cRecord.Score || (o.Score == cRecord.Score && o.Duration > cRecord.Duration)
            );
            return cRecord.Convert();
        }

        void ReStartClicked(object sender, EventArgs args)
        {
            game.Init();
            InitLayout();
        }

        void OnAboutButtonClicked(object sender, EventArgs args)
        {
            if(!mask.Contains("about"))
            {
                var aboutView = ControlUtil.GetAbout(gameInfo);
                mask.Add("about", gameInfo.Name, aboutView);
            }
            mask.ShowWithCancel("about", "确定");
        }

        void OnRecordClicked(object sender, EventArgs args)
        {
            //var sorter = new Sorter<FlipRecord>(q => q.OrderByDescending(x => x.Score).ThenBy(x => x.Duration).ThenByDescending(x => x.ID));
            Navigation.PushAsync(new RecordPage(gameInfo.Name, new RecordViewModel<FlipRecord>(null)));
        }

        private void TapToBack(object sender, EventArgs e)
        {
            Navigation.PopAsync();
        }
        protected override bool OnBackButtonPressed()
        {
            return true;
        }
    }
}